package afero

import (
	"io/fs"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"time"
)

var (
	_ Lstater        = (*BasePathFs)(nil)
	_ fs.ReadDirFile = (*BasePathFile)(nil)
)

// The BasePathFs restricts all operations to a given path within an Fs.
// The given file name to the operations on this Fs will be prepended with
// the base path before calling the base Fs.
// Any file name (after filepath.Clean()) outside this base path will be
// treated as non existing file.
//
// Note that it does not clean the error messages on return, so you may
// reveal the real path on errors.
type BasePathFs struct {
	source    Fs
	path      string
	xinkePath string
}

type BasePathFile struct {
	File
	path string
}

func (f *BasePathFile) Name() string {
	sourcename := f.File.Name()
	return strings.TrimPrefix(sourcename, filepath.Clean(f.path))
}

func (f *BasePathFile) ReadDir(n int) ([]fs.DirEntry, error) {
	if rdf, ok := f.File.(fs.ReadDirFile); ok {
		return rdf.ReadDir(n)
	}
	return readDirFile{f.File}.ReadDir(n)
}

func NewBasePathFs(source Fs, path string) Fs {
	return &BasePathFs{source: source, path: path}
}

func (b *BasePathFs) CreateRootPath(path string) (err error) {

	/*fs1 := afero.NewOsFs()
	_, err := fs1.Stat(path)
	if os.IsNotExist(err){
		err = fs.MkdirAll(path, 0755)

	}*/
	if _, err := b.source.Stat(path); err != nil {
		err = b.source.MkdirAll(path, 0755)
		if err != nil {
			return err
		}
	}

	return nil
}

func NewBasePathXinkeFs(source Fs, path string, xinkePath string) Fs {

	return &BasePathFs{source: source, path: path, xinkePath: xinkePath}
}

// on a file outside the base path it returns the given file name and an error,
// else the given file with the base path prepended
func (b *BasePathFs) RealPath(name string) (path string, err error) {

	if b.xinkePath == "" {
		if err := validateBasePathName(name); err != nil {
			return name, err
		}

		bpath := filepath.Clean(b.path)
		path = filepath.Clean(filepath.Join(bpath, name))
		if !strings.HasPrefix(path, bpath) {
			return name, os.ErrNotExist
		}

		return path, nil
	}
	return b.XinkeRealPath(name)

}

func (b *BasePathFs) RealRootPath(name string) (path string, err error) {

	if err := validateBasePathName(name); err != nil {
		return name, err
	}

	if b.path != "" {
		err = b.CreateRootPath(b.path)
		if err != nil {
			return b.path, err
		}

	}

	bpath := filepath.Clean(b.path)
	path = filepath.Clean(filepath.Join(bpath, name))
	if !strings.HasPrefix(path, bpath) {
		return name, os.ErrNotExist
	}

	return path, nil

}

/*
func (b *BasePathFs) XinkeJugdePath(name string) (flag bool, path string, err error) {
	//path = \$XinKe$\2.exe
	//path = \base\tmp\$XinKe$\2.exe
	//目录是从根目录开始
	//返回一个真实 从根目录开始的路径
	var s1 string = "/<XinKe>"
	var s2 string = "<XinKe>"
	var s3 string = "\\<XinKe>"
	var dirPath string = ""

	flag = false

	fmt.Printf("new name:%s \n", name)
	fmt.Printf("b.xinkePath:%s \n", b.xinkePath)

	if strings.Contains(name, s1) || strings.Contains(name, s2) || strings.Contains(name, s3) {
		flag = true
		if strings.Contains(name, s1) {
			path = strings.Replace(name, s1, "", -1)
		} else if strings.Contains(name, s2) {
			path = strings.Replace(name, s2, "", -1)
		} else if strings.Contains(name, s3) {
			path = strings.Replace(name, s3, "", -1)
		}

		dirPath = strings.TrimSuffix(path, strings.Split(path, "/")[len(strings.Split(path, "/"))-1])
		bpath := filepath.Clean(b.xinkePath + dirPath)

		if _, err := os.Stat(bpath); os.IsNotExist(err) {
			os.MkdirAll(bpath, os.ModePerm)
		}
		path = filepath.Clean(filepath.Join(b.xinkePath, path))

		path = filepath.Clean(path)

	} else {
		path = name
	}

	return flag, path, nil
}*/

func (b *BasePathFs) XinkeRealPath(name string) (path string, err error) {
	//path = \$XinKe$\2.exe
	if err := validateBasePathName(name); err != nil {
		return name, err
	}

	if b.xinkePath != "" {
		err = b.CreateRootPath(b.xinkePath)
		if err != nil {
			return b.path, err
		}

	}

	bpath := filepath.Clean(b.xinkePath)
	if _, err := os.Stat(bpath); os.IsNotExist(err) {
		os.Mkdir(bpath, os.ModePerm)
	}
	path = filepath.Clean(filepath.Join(bpath, name))
	if !strings.HasPrefix(path, bpath) {
		return b.path, os.ErrNotExist
	}

	return path, nil
}

func validateBasePathName(name string) error {
	if runtime.GOOS != "windows" {
		// Not much to do here;
		// the virtual file paths all look absolute on *nix.
		return nil
	}

	// On Windows a common mistake would be to provide an absolute OS path
	// We could strip out the base part, but that would not be very portable.
	if filepath.IsAbs(name) {
		return os.ErrNotExist
	}

	return nil
}

func (b *BasePathFs) Chtimes(name string, atime, mtime time.Time) (err error) {
	if name, err = b.RealPath(name); err != nil {
		return &os.PathError{Op: "chtimes", Path: name, Err: err}
	}
	return b.source.Chtimes(name, atime, mtime)
}

func (b *BasePathFs) Chmod(name string, mode os.FileMode) (err error) {
	if name, err = b.RealPath(name); err != nil {
		return &os.PathError{Op: "chmod", Path: name, Err: err}
	}
	return b.source.Chmod(name, mode)
}

func (b *BasePathFs) Chown(name string, uid, gid int) (err error) {
	if name, err = b.RealPath(name); err != nil {
		return &os.PathError{Op: "chown", Path: name, Err: err}
	}
	return b.source.Chown(name, uid, gid)
}

func (b *BasePathFs) Name() string {
	return "BasePathFs"
}

func (b *BasePathFs) Stat(name string) (fi os.FileInfo, err error) {

	if name, err = b.RealRootPath(name); err != nil {
		return nil, &os.PathError{Op: "stat", Path: name, Err: err}
	}

	/*if name, err = b.RealPath(name); err != nil {
		return nil, &os.PathError{Op: "stat", Path: name, Err: err}
	}*/
	return b.source.Stat(name)
}

func (b *BasePathFs) Rename(oldname, newname string) (err error) {

	if oldname, err = b.RealPath(oldname); err != nil {
		return &os.PathError{Op: "rename", Path: oldname, Err: err}
	}
	/*
		在这里就判断newname参数中是否有$XinKe$
		正常重名参数 rename \tmp\1.exe 2.exe
		sdk上传参数 存储到另外的位置  rename \tmp\1.exe \tt\<XinKe>\2.exe
	*/

	/*var flag = false
	fmt.Printf("new name:%s \n", newname)

	if flag, newname, err = b.XinkeJugdePath(newname); flag == false || err != nil {

		if newname, err = b.RealPath(newname); err != nil {
			return &os.PathError{Op: "rename", Path: newname, Err: err}
		}
	}*/

	//通过前缀 判断是否需要rename到新目录中去
	return b.source.Rename(oldname, newname)
}

func (b *BasePathFs) RemoveAll(name string) (err error) {
	if name, err = b.RealPath(name); err != nil {
		return &os.PathError{Op: "remove_all", Path: name, Err: err}
	}
	return b.source.RemoveAll(name)
}

/*func (b *BasePathFs) Remove(name string) (err error) {
	if name, err = b.RealPath(name); err != nil {
		return &os.PathError{Op: "remove", Path: name, Err: err}
	}
	return b.source.Remove(name)
}*/
/*
func (b *BasePathFs) XinkeRemovePath(name string) (flag bool, path string, err error) {
	//path = \$XinKe$\2.exe
	//path = \base\tmp\$XinKe$\2.exe
	//目录是从根目录开始
	//返回一个真实 从根目录开始的路径
	var s1 string = "/<XinKe>"
	var s2 string = "<XinKe>"
	var s3 string = "\\<XinKe>"
	//var dirPath string = ""
	var lastIndex int = 0

	flag = false

	//fmt.Printf("new name:%s \n", name)
	//fmt.Printf("b.xinkePath:%s \n", b.xinkePath)

	if strings.Contains(name, s1) || strings.Contains(name, s2) || strings.Contains(name, s3) {
		flag = true

		if strings.Contains(name, s1) {

			lastIndex = strings.LastIndex(name, s1) + len(s1)
			fmt.Printf("name :%s \n", name[lastIndex:])

		} else if strings.Contains(name, s2) {

			lastIndex = strings.LastIndex(name, s2) + len(s2)
			fmt.Printf("name :%s \n", name[lastIndex:])

		} else if strings.Contains(name, s3) {

			lastIndex = strings.LastIndex(name, s3) + len(s3)
			fmt.Printf("name :%s \n", name[lastIndex:])

		}

		path = name[lastIndex:]

		//截取标志符合 以后的数据
		path = filepath.Clean(filepath.Join(b.xinkePath, path))

	} else {
		path = name
	}

	return flag, path, nil
}*/

func (b *BasePathFs) Remove(name string) (err error) {
	baseName := name
	if name, err = b.RealPath(name); err != nil {
		return &os.PathError{Op: "remove", Path: name, Err: err}
	}

	if err = b.source.RemoveAll(name); err != nil {
		return &os.PathError{Op: "remove", Path: name, Err: err}
	}

	if baseName, err = b.RealRootPath(baseName); err != nil {
		return &os.PathError{Op: "mkdir", Path: name, Err: err}
	}

	return b.source.RemoveAll(baseName)

}

/*
func (b *BasePathFs) OpenFile(name string, flag int, mode os.FileMode) (f File, err error) {
	if name, err = b.RealPath(name); err != nil {
		return nil, &os.PathError{Op: "openfile", Path: name, Err: err}
	}

	sourcef, err := b.source.OpenFile(name, flag, mode)
	if err != nil {
		return nil, err
	}
	return &BasePathFile{sourcef, b.path}, nil
}*/

func (b *BasePathFs) OpenFile(name string, flag int, mode os.FileMode) (f File, err error) {
	if name, err = b.RealPath(name); err != nil {
		return nil, &os.PathError{Op: "openfile", Path: name, Err: err}
	}

	dirPath := filepath.Dir(name)
	if err = b.CreateRootPath(dirPath); err != nil {
		return nil, &os.PathError{Op: "openfile", Path: name, Err: err}
	}

	sourcef, err := b.source.OpenFile(name, flag, mode)
	if err != nil {
		return nil, err
	}
	return &BasePathFile{sourcef, b.xinkePath}, nil
}

func (b *BasePathFs) Open(name string) (f File, err error) {

	if name, err = b.RealRootPath(name); err != nil {
		return nil, &os.PathError{Op: "open", Path: name, Err: err}
	}
	/*
		if name, err = b.RealPath(name); err != nil {
			return nil, &os.PathError{Op: "open", Path: name, Err: err}
		}
	*/

	sourcef, err := b.source.Open(name)
	if err != nil {
		return nil, err
	}
	return &BasePathFile{File: sourcef, path: b.path}, nil
}

func (b *BasePathFs) Mkdir(name string, mode os.FileMode) (err error) {
	baseName := name
	if name, err = b.RealPath(name); err != nil {
		return &os.PathError{Op: "mkdir", Path: baseName, Err: err}
	}

	if err = b.source.Mkdir(name, mode); err != nil {
		return &os.PathError{Op: "mkdir", Path: baseName, Err: err}
	}

	if baseName, err = b.RealRootPath(baseName); err != nil {
		return &os.PathError{Op: "mkdir", Path: baseName, Err: err}
	}
	return b.source.Mkdir(baseName, mode)
	//这里需要调用 mkdirall 可以遍历创建目录 mkdir 只能创建当前目录不能遍历创建目录
	//return b.source.MkdirAll(name, mode)
}

func (b *BasePathFs) MkdirAll(name string, mode os.FileMode) (err error) {
	if name, err = b.RealPath(name); err != nil {
		return &os.PathError{Op: "mkdir", Path: name, Err: err}
	}
	return b.source.MkdirAll(name, mode)
}

func (b *BasePathFs) Create(name string) (f File, err error) {
	if name, err = b.RealPath(name); err != nil {
		return nil, &os.PathError{Op: "create", Path: name, Err: err}
	}
	sourcef, err := b.source.Create(name)
	if err != nil {
		return nil, err
	}
	return &BasePathFile{File: sourcef, path: b.path}, nil
}

func (b *BasePathFs) LstatIfPossible(name string) (os.FileInfo, bool, error) {
	name, err := b.RealPath(name)
	if err != nil {
		return nil, false, &os.PathError{Op: "lstat", Path: name, Err: err}
	}
	if lstater, ok := b.source.(Lstater); ok {
		return lstater.LstatIfPossible(name)
	}
	fi, err := b.source.Stat(name)
	return fi, false, err
}

func (b *BasePathFs) SymlinkIfPossible(oldname, newname string) error {
	oldname, err := b.RealPath(oldname)
	if err != nil {
		return &os.LinkError{Op: "symlink", Old: oldname, New: newname, Err: err}
	}
	newname, err = b.RealPath(newname)
	if err != nil {
		return &os.LinkError{Op: "symlink", Old: oldname, New: newname, Err: err}
	}
	if linker, ok := b.source.(Linker); ok {
		return linker.SymlinkIfPossible(oldname, newname)
	}
	return &os.LinkError{Op: "symlink", Old: oldname, New: newname, Err: ErrNoSymlink}
}

func (b *BasePathFs) ReadlinkIfPossible(name string) (string, error) {
	name, err := b.RealPath(name)
	if err != nil {
		return "", &os.PathError{Op: "readlink", Path: name, Err: err}
	}
	if reader, ok := b.source.(LinkReader); ok {
		return reader.ReadlinkIfPossible(name)
	}
	return "", &os.PathError{Op: "readlink", Path: name, Err: ErrNoReadlink}
}
